package bsh.utils.reflection.transform;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.CompilationState;
import bsh.meta.JavaClassWrapper;
import bsh.meta.TypeTransformation;
import bsh.meta.TypeWrapper;

/**
 * Todas as {@link TypeTransformation} que aumentam uma primitiva (short -> int)
 * 
 * @author takeshi
 */
public final class PrimitiveWideningTypeTransformations implements TypeTransformation {

    private final TypeWrapper fromType, toType;

    private final int opcode;

    /**
     * @param fromType class
     * @param toType class
     * @param opcode opcode para a transformacao
     */
    private PrimitiveWideningTypeTransformations(Class<?> fromType, Class<?> toType, int opcode) {
        super();
        this.opcode = opcode;
        this.fromType = JavaClassWrapper.wrapperFor(fromType);
        this.toType = JavaClassWrapper.wrapperFor(toType);
    }

    /**
     * {@inheritDoc}
     */
    public boolean acceptsType(TypeWrapper type) {
        return type.equals(this.fromType);
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper toType(TypeWrapper from) {
        return this.toType;
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper transformStackTop(MethodVisitor visitor, TypeWrapper from, CompilationState state) {
        state.popStack();
        if (this.opcode >= 0) {
            visitor.visitInsn(this.opcode);
        }
        state.pushStack(this.toType);
        return this.toType;
    }

    public static final PrimitiveWideningTypeTransformations BYTE_INT_WIDENING = new PrimitiveWideningTypeTransformations(byte.class,
            int.class, -1);

    public static final PrimitiveWideningTypeTransformations BYTE_SHORT_WIDENING = new PrimitiveWideningTypeTransformations(byte.class,
            short.class, Opcodes.I2S);

    public static final PrimitiveWideningTypeTransformations BYTE_CHAR_WIDENING = new PrimitiveWideningTypeTransformations(byte.class,
            char.class, Opcodes.I2C);

    public static final PrimitiveWideningTypeTransformations BYTE_LONG_WIDENING = new PrimitiveWideningTypeTransformations(byte.class,
            long.class, Opcodes.I2L);

    public static final PrimitiveWideningTypeTransformations BYTE_FLOAT_WIDENING = new PrimitiveWideningTypeTransformations(byte.class,
            float.class, Opcodes.I2F);

    public static final PrimitiveWideningTypeTransformations BYTE_DOUBLE_WIDENING = new PrimitiveWideningTypeTransformations(byte.class,
            double.class, Opcodes.I2D);

    public static final PrimitiveWideningTypeTransformations SHORT_CHAR_WIDENING = new PrimitiveWideningTypeTransformations(short.class,
            char.class, Opcodes.I2C);

    public static final PrimitiveWideningTypeTransformations SHORT_INT_WIDENING = new PrimitiveWideningTypeTransformations(short.class,
            int.class, -1);

    public static final PrimitiveWideningTypeTransformations SHORT_LONG_WIDENING = new PrimitiveWideningTypeTransformations(short.class,
            long.class, Opcodes.I2L);

    public static final PrimitiveWideningTypeTransformations SHORT_FLOAT_WIDENING = new PrimitiveWideningTypeTransformations(short.class,
            float.class, Opcodes.I2F);

    public static final PrimitiveWideningTypeTransformations SHORT_DOUBLE_WIDENING = new PrimitiveWideningTypeTransformations(short.class,
            double.class, Opcodes.I2D);

    public static final PrimitiveWideningTypeTransformations CHAR_INT_WIDENING = new PrimitiveWideningTypeTransformations(char.class,
            int.class, -1);

    public static final PrimitiveWideningTypeTransformations CHAR_LONG_WIDENING = new PrimitiveWideningTypeTransformations(char.class,
            long.class, Opcodes.I2L);

    public static final PrimitiveWideningTypeTransformations CHAR_FLOAT_WIDENING = new PrimitiveWideningTypeTransformations(char.class,
            float.class, Opcodes.I2F);

    public static final PrimitiveWideningTypeTransformations CHAR_DOUBLE_WIDENING = new PrimitiveWideningTypeTransformations(char.class,
            double.class, Opcodes.I2D);

    public static final PrimitiveWideningTypeTransformations INT_LONG_WIDENING = new PrimitiveWideningTypeTransformations(int.class,
            long.class, Opcodes.I2L);

    public static final PrimitiveWideningTypeTransformations INT_FLOAT_WIDENING = new PrimitiveWideningTypeTransformations(int.class,
            float.class, Opcodes.I2F);

    public static final PrimitiveWideningTypeTransformations INT_DOUBLE_WIDENING = new PrimitiveWideningTypeTransformations(int.class,
            double.class, Opcodes.I2D);

    public static final PrimitiveWideningTypeTransformations LONG_FLOAT_WIDENING = new PrimitiveWideningTypeTransformations(long.class,
            double.class, Opcodes.L2F);

    public static final PrimitiveWideningTypeTransformations LONG_DOUBLE_WIDENING = new PrimitiveWideningTypeTransformations(long.class,
            double.class, Opcodes.L2D);

    public static final PrimitiveWideningTypeTransformations FLOAT_DOUBLE_WIDENING = new PrimitiveWideningTypeTransformations(float.class,
            double.class, Opcodes.F2D);

    private static final PrimitiveWideningTypeTransformations[] ALL_TRANSFORMATIONS = { BYTE_CHAR_WIDENING, BYTE_DOUBLE_WIDENING,
            BYTE_FLOAT_WIDENING, BYTE_INT_WIDENING, BYTE_LONG_WIDENING, BYTE_SHORT_WIDENING, SHORT_CHAR_WIDENING, SHORT_DOUBLE_WIDENING,
            SHORT_FLOAT_WIDENING, SHORT_INT_WIDENING, SHORT_LONG_WIDENING, CHAR_DOUBLE_WIDENING, CHAR_FLOAT_WIDENING, CHAR_INT_WIDENING,
            CHAR_LONG_WIDENING, INT_DOUBLE_WIDENING, INT_FLOAT_WIDENING, INT_LONG_WIDENING, LONG_DOUBLE_WIDENING, LONG_FLOAT_WIDENING,
            FLOAT_DOUBLE_WIDENING };

    /**
     * Devolve todas as transformacoes de primitivas
     * 
     * @return List
     */
    public static List<PrimitiveWideningTypeTransformations> allTransformations() {
        return new ArrayList<PrimitiveWideningTypeTransformations>(Arrays.asList(ALL_TRANSFORMATIONS));
    }

    /**
     * Devolve todas as transformacoes de primitivas de from
     * 
     * @param from {@link Class}
     * @return List
     */
    public static List<PrimitiveWideningTypeTransformations> allTransformationsFrom(Class<?> from) {
        List<PrimitiveWideningTypeTransformations> list = new ArrayList<PrimitiveWideningTypeTransformations>();
        for (PrimitiveWideningTypeTransformations tx : ALL_TRANSFORMATIONS) {
            if (JavaClassWrapper.unwrapJavaClass(tx.fromType).equals(from)) {
                list.add(tx);
            }
        }
        return list;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return this.fromType + " -> " + this.toType;
    }

    /**
     * @return the fromType
     */
    public TypeWrapper getFromType() {
        return fromType;
    }

    /**
     * @return the toType
     */
    public TypeWrapper getToType() {
        return toType;
    }

    /**
     * @return the opcode
     */
    public int getOpcode() {
        return opcode;
    }

}
